Skip to main content

Audio Stream

In this tutorial we set up and play a continuous audio waveform using a buffer and event-driven playback mechanism.

Download original notebook

There is only one built-in option to stream raw audio data - PCMPlayer, which accepts a audio buffers in a form of NumericArray of singed integers (as most audio sources):

abuffer = NumericArray[0.2 RandomInteger[{-32760,32760}, 40 1024], "Integer16", "ClipAndRound"];
PCMPlayer[abuffer, "SignedInteger16"]
warning

This will generate a second of a white noise

For further example we generate audio on-fly using samplingFunction, which defines a pleasant waveform to be played:

samplingFunction = Function[t, Sin[200.0 t]]

In this case, it’s a sine wave of frequency 200 Hz.

For continuous generation we need to track global time and supply new chunks of data to PCMPlayer. The latter is event-driven and it will fire an event before the previous chunk of data is almost depleted:

buffer = NumericArray[Table[0, {256}], "Integer16", "ClipAndRound"];
samplingFunction = Function[t, Sin[200.0 t]];

time = 0;

EventHandler["bufferEnds", {"More" -> Function[Null,
  With[{c = (1.0/44100.0) (2Pi)},
    With[{sampled = 32760 Table[samplingFunction[c (i + time)], {i, 0, 4 1024 - 1}]},
      buffer = NumericArray[sampled, "Integer16", "ClipAndRound"];
    ];
    
    time += 4 1024;
  ];
]}];

PCMPlayer[buffer // Offload, "SignedInteger16", "Event"->"bufferEnds"]

We can change the generator function on-fly

samplingFunction = Function[t, Sin[300.0 t ]];

Two oscillators with a decay

samplingFunction = Function[t, With[{mod = Mod[t, Pi]},
  Sin[300.0 t ] Sin[45.0 t ] Exp[-t mod / 200.0 ]
]];

Waveform screen

Let's modify our handler function to store waveform in ilines symbol

EventHandler["bufferEnds", {"More" -> Function[Null,
  With[{c = (1.0/44100.0) (2Pi)},
    With[{sampled = 32760 Table[samplingFunction[c (i + time)], {i, 0, 4 1024 - 1}]},
      buffer = NumericArray[sampled, "Integer16", "ClipAndRound"];
      ilines = NumericArray[
        Transpose[{Range[4 1024], sampled}]
      , "Integer16", "ClipAndRound"];
    ];
    
    time += 4 1024;
  ];
]}];
tip

Project the cell below to a window

Graphics[{Line[ilines // Offload]}, "TransitionType"->None, AspectRatio->0.5, "Controls"->False, PlotRange->{{0, 4 1024}, {-32760, 32760}}]

GUI controls

Here we set up a bunch of sliders to control the oscillator parameters

EventHandler[InputGroup[<|
  "w1" -> InputRange[10, 800, 1, 455, "Label"->"Osc 1"],
  "w2" -> InputRange[10, 800, 1, 455, "Label"->"Osc 2"],
  "d"  -> InputRange[50, 1000, 10, 300, "Label"->"Decay"],
  "r"  -> InputRange[0.5, 4.5, 0.1, 3.1, "Label"->"Period"]
|>, "Layout"->"Horisontal"], With[{},
  samplingFunction = Function[t, With[{mod = Mod[t, #r]},
    Sin[#w1 t ] Sin[#w2 t ] Exp[-t mod / #d ] 
  ]];
]&]

See it in action ✨